import pygtk, gtk
import pango, cairo, atk, pangocairo
import sys, re, os, warnings
import webbrowser, smtplib

from ConfigParser import ConfigParser
from interface import Interface as gui
from pysqlite2 import dbapi2 as sql
from datetime import date

VERSION         = '1.2'

CAR_DIBUJO      = 0
CAR_DESCRIPCION = 1
CAR_ESPESOR     = 2
CAR_ANCHO       = 3
CAR_LARGO       = 4
CAR_FIGURA      = 5
CAR_EXISTENCIA  = 6
CAR_PRECIO      = 7

PRO_MINIATURA   = 0
PRO_CATALOGO    = 1
PRO_MARCA       = 2
PRO_CODIGO      = 3
PRO_PRODUCTO    = 4
PRO_ESPESOR     = 5
PRO_ANCHO       = 6
PRO_LARGO       = 7
PRO_FIGURA      = 8
PRO_EXISTENCIA  = 9
PRO_PRECIO      = 10

ITM_MINIATURA   = 0
ITM_CANTIDAD    = 1
ITM_DETALLE     = 2
ITM_PARCIAL     = 3
ITM_TOTAL       = 4
ITM_ESPESOR     = 5
ITM_ANCHO       = 6
ITM_LARGO       = 7
ITM_FIGURA      = 8

PRE_NUMERO      = 0
PRE_FECHA       = 1
PRE_CONTACTO    = 2
PRE_ESTADO      = 3
PRE_TOTAL       = 4
PRE_FRENTE      = 5
PRE_FONDO       = 6
PRE_FUENTE      = 7

TODOS           = 'TODOS LOS CATÁLOGOS'

class Configuracion:
    class colores:
        class frente:
            pendiente = '#000000000000'
            aprobado  = '#000000000000'
            cumplido  = '#000000000000'
            rechazado = '#000000000000'
        class fondo:
            pendiente = '#FFFFFFFFFFFF'
            aprobado  = '#FFFFFFFFFFFF'
            cumplido  = '#FFFFFFFFFFFF'
            rechazado = '#FFFFFFFFFFFF'
    class fuentes:
        pendiente = 'Sans Bold 12'
        aprobado  = 'Sans 12'
        cumplido  = 'Sans 12'
        rechazado = 'Sans 12'
    cargar    = False
    duplicado = False
    minimo    = 0
    remitente = ''
    servidor  = 'localhost'

base          = './figuras'
extension     = 'png'
configuracion = Configuracion ()
parametros    = None
contexto_id   = None
mensaje_id    = None
icono         = None
conexion      = None
cursor        = None
detener       = 0
carbones      = gtk.ListStore (gtk.gdk.Pixbuf, str, float, float, float, int, int, float)
productos     = gtk.ListStore (gtk.gdk.Pixbuf, str, str, str, str, str, float, float, float, int, int, float)
items         = gtk.ListStore (gtk.gdk.Pixbuf, int, str, float, float, float, float, float, int)
presupuestos  = gtk.ListStore (int, str, str, str, str, str, str, str)

def importar (nombre):
    archivo = open (nombre, "r")

    sql = archivo.read ()
    sql = sql.decode ('iso-8859-1', 'ignore')
    archivo.close ()

    comandos = sql.split (';')

    interface.ventana.progreso.show ()
    interface.ventana.detener.show ()
    while gtk.events_pending (): gtk.main_iteration ()

    global detener
    errores  = 0
    contador = 0
    cantidad = len (comandos)

    if not cantidad:
        informar ('No se encontró ningún comando SQL en el archivo.' % errores, gtk.STOCK_DIALOG_ERROR)
        return

    for comando in comandos:
        if detener: break

        contador    += 1
        fraccion    = float (contador) / float (cantidad)
        porcentaje  = fraccion * 100
        informar ('Ejecutando comando SQL %d de %d' % (contador, cantidad))
        interface.ventana.progreso.set_text ('%d%%' % porcentaje)
        interface.ventana.progreso.set_fraction (fraccion)
        while gtk.events_pending (): gtk.main_iteration ()

        try:
            cursor.execute (comando)
        except Exception, mensaje:
            print 'Ocurrió el siguiente error al intentar ejecutar el comando SQL "%s", %s.' % (comando, mensaje)
            errores += 1

    if errores:
        if errores > 1:
            informar ('Listo, se terminó de ejecutar el archivo de comandos SQL pero ocurrieron %d errores.' % errores, gtk.STOCK_DIALOG_ERROR)
        else:
            informar ('Listo, se terminó de ejecutar el archivo de comandos SQL pero ocurrió un error.', gtk.STOCK_DIALOG_ERROR)
    else:
        if cantidad > 1:
            informar ('Listo, se terminaron de ejecutar %d comandos del archivo de comandos SQL.' % (cantidad - errores), gtk.STOCK_DIALOG_ERROR)
        else:
            informar ('Listo, se terminó de ejecutar el comando del archivo de comandos SQL.', gtk.STOCK_DIALOG_ERROR)

    interface.ventana.progreso.hide ()
    interface.ventana.detener.hide ()

def activar_icono_aplicacion (*args):
    visible = interface.ventana.get_property ("visible")
    interface.ventana.set_property ("visible", not visible)

def error (mensaje, descripcion):
    if interface:
        if interface.formulario.get_property ('visible'):
            ventana = interface.formulario
        else:
            ventana = interface.ventana
    else:
        ventana = None

    dialogo = gtk.MessageDialog (ventana,
                gtk.DIALOG_MODAL |
                gtk.DIALOG_DESTROY_WITH_PARENT,
            gtk.MESSAGE_ERROR,
            gtk.BUTTONS_CLOSE,
            mensaje
        )

    dialogo.set_title ("Error")
    dialogo.format_secondary_markup (descripcion)
    dialogo.set_position (gtk.WIN_POS_CENTER_ON_PARENT)
    dialogo.run ()
    dialogo.destroy ()

def preguntar (mensaje, descripcion):
    if interface.formulario.get_property ('visible'):
        ventana = interface.formulario
    else:
        ventana = interface.ventana

    dialogo = gtk.MessageDialog (ventana,
                gtk.DIALOG_MODAL |
                gtk.DIALOG_DESTROY_WITH_PARENT,
            gtk.MESSAGE_QUESTION,
            gtk.BUTTONS_YES_NO,
            mensaje
        )

    dialogo.set_title ("Pregunta")
    dialogo.format_secondary_text (descripcion)
    dialogo.set_position (gtk.WIN_POS_CENTER_ON_PARENT)
    respuesta = dialogo.run ()
    dialogo.destroy ()
    if respuesta == gtk.RESPONSE_YES:
        return True
    else:
        return False

def informar (mensaje = None, icono = gtk.STOCK_DIALOG_ERROR):
    global contexto_id
    global mensaje_id
    if contexto_id and mensaje_id:
        interface.ventana.barra_estado.pop (contexto_id)
        interface.ventana.barra_estado.remove (contexto_id, mensaje_id)

    if not mensaje:
        return

    contexto_id = interface.ventana.barra_estado.get_context_id ('mensajes')
    mensaje_id  = interface.ventana.barra_estado.push (contexto_id, mensaje)

def obtener_descripcion (camino):
    espesor    = carbones [camino] [CAR_ESPESOR]
    ancho      = carbones [camino] [CAR_ANCHO]
    largo      = carbones [camino] [CAR_LARGO]
    figura     = carbones [camino] [CAR_FIGURA]
    existencia = carbones [camino] [CAR_EXISTENCIA]
    precio     = carbones [camino] [CAR_PRECIO]

    descripcion  = "<b>Espesor:</b> %.02f mm\n" % espesor
    descripcion += "<b>Ancho:</b> %.02f mm\n"   % ancho
    descripcion += "<b>Largo:</b> %.02f mm\n"   % largo
    descripcion += "<b>Figura:</b> %d\n"       % figura

    if existencia: descripcion += "<b>Existencia:</b> %d\n" % int (existencia)
    if precio:     descripcion += "<b>Precio:</b> %.02f\n"  % float (precio)

    return descripcion

def corregir_alto_celda (celda, editable, camino):
    editable.set_size_request (-1, 20)

def formatear_celda (columna, celda, modelo, iter, n_columna):
    valor = modelo.get_value (iter, n_columna)
    if not valor: valor = 0
    celda.set_property ('text', '%.02f' % float (valor))

def formatear_celda_numero (columna, celda, modelo, iter, n_columna):
    valor = modelo.get_value (iter, n_columna)
    celda.set_property ('text', '%s' % str (valor).zfill (10))

def calcular_total ():
    total = 0
    for precio in items:
        total += precio [ITM_TOTAL]
    interface.ventana.total.set_text ('%.02f' % total)

def dibujar_pagina (operacion, contexto, pagina, texto):
        lienzo = contexto.get_cairo_context ()
        fuente = pango.FontDescription ("Courier 14")
        capa   = contexto.create_pango_layout ()

        capa.set_font_description (fuente)
        capa.set_text (texto)

        lienzo.show_layout (capa)

def imprimir (texto):
    global parametros

    operacion = gtk.PrintOperation ()

    if parametros != None:
        operacion.set_print_settings (parametros)

    # esto es mejor ponerlo en la retrollamada 'begin-print'
    operacion.set_n_pages (1)

    operacion.connect ('draw-page', dibujar_pagina, texto)

    resultado = operacion.run (gtk.PRINT_OPERATION_ACTION_PRINT_DIALOG, interface.ventana)

    if resultado == gtk.PRINT_OPERATION_RESULT_ERROR:
        error ('Error en la operación de impresión', 'No se pueden imprimir los datos')
    elif resultado == gtk.PRINT_OPERATION_RESULT_APPLY:
        parametros = operacion.get_print_settings ()

def sincronizar_datos (camino):
    catalogo   = productos [camino] [PRO_CATALOGO]
    marca      = productos [camino] [PRO_MARCA]
    codigo     = productos [camino] [PRO_CODIGO]
    producto   = productos [camino] [PRO_PRODUCTO]
    espesor    = productos [camino] [PRO_ESPESOR]
    ancho      = productos [camino] [PRO_ANCHO]
    largo      = productos [camino] [PRO_LARGO]
    figura     = productos [camino] [PRO_FIGURA]
    existencia = productos [camino] [PRO_EXISTENCIA]
    precio     = productos [camino] [PRO_PRECIO]

    print "El catálogo es ", catalogo

    consulta  = "insert into carbones ("
    consulta += "espesor,"
    consulta += "ancho,"
    consulta += "largo,"
    consulta += "figura,"
    consulta += "existencia,"
    consulta += "precio"
    consulta += ") values ("
    consulta += "%.2f," % float (espesor)
    consulta += "%.2f," % float (ancho)
    consulta += "%.2f," % float (largo)
    consulta += "%d,"   % int   (figura)
    consulta += "%d,"   % int   (existencia)
    consulta += "%.2f"  % float (precio)
    consulta += ")"

    try:
        cursor.execute (consulta)
    except sql.IntegrityError:
        consulta  = "update carbones set "
        consulta += "espesor    = %.2f," % float (espesor)
        consulta += "ancho      = %.2f," % float (ancho)
        consulta += "largo      = %.2f," % float (largo)
        consulta += "figura     = %d,"   % int   (figura)
        consulta += "existencia = %d,"   % int   (existencia)
        consulta += "precio     = %.2f " % float (precio)
        consulta += "where "
        consulta += "espesor    = %.2f and " % float (espesor)
        consulta += "ancho      = %.2f and " % float (ancho)
        consulta += "largo      = %.2f and " % float (largo)
        consulta += "figura     = %d"        % int   (figura)
    except:
        mensaje  = "Mientras se ejecutaba la aplicación ocurrió un error inesperado con origen en la excepción <b>%s</b> "
        mensaje += "y devolvió el siguiente error:\n\n<i>%s</i>."
        error ("Error inesperado", mensaje % (sys.exc_info () [0], sys.exc_info () [1]))
        return

        try:
            cursor.execute (consulta)
        except:
            mensaje  = "Mientras se ejecutaba la aplicación ocurrió un error inesperado con origen en la excepción <b>%s</b> "
            mensaje += "y devolvió el siguiente error:\n\n<i>%s</i>."
            error ("Error inesperado", mensaje % (sys.exc_info () [0], sys.exc_info () [1]))
            return

        consulta  = "insert into productos ("
        consulta += "catalogo,"
        consulta += "marca,"
        consulta += "codigo,"
        consulta += "producto,"
        consulta += "espesor,"
        consulta += "ancho,"
        consulta += "largo,"
        consulta += "figura"
        consulta += ") select "
        consulta += "cat.catalogo,"
        consulta += "'%s'," % marca.upper ()
        consulta += "'%s'," % codigo.upper ()
        consulta += "'%s'," % producto.upper ()
        consulta += "car.espesor,"
        consulta += "car.ancho,"
        consulta += "car.largo,"
        consulta += "car.figura "
        consulta += "from carbones car, catalogos cat where "
        consulta += "car.espesor  = %.2f and " % float (espesor)
        consulta += "car.ancho    = %.2f and " % float (ancho)
        consulta += "car.largo    = %.2f and " % float (largo)
        consulta += "car.figura   = %d and "   % int   (figura)
        consulta += "cat.catalogo = '%s'"      % catalogo.upper ()

    try:
        cursor.execute (consulta)
    except sql.IntegrityError:
        consulta  = "update productos set "
        consulta += "catalogo = '%s'," % catalogo.upper ()
        consulta += "marca    = '%s'," % marca.upper ()
        consulta += "codigo   = '%s'," % codigo.upper ()
        consulta += "producto = '%s'," % producto.upper ()
        consulta += "espesor  = %.2f," % float (espesor)
        consulta += "ancho    = %.2f," % float (ancho)
        consulta += "largo    = %.2f," % float (largo)
        consulta += "figura   = %d "   % int   (figura)
        consulta += "where "
        consulta += "catalogo = '%s' and " % catalogo
        consulta += "marca    = '%s' and " % marca
        consulta += "producto = '%s'"      % producto

        cursor.execute (consulta)
    except:
        mensaje  = "Mientras se ejecutaba la aplicación ocurrió un error inesperado con origen en la excepción <b>%s</b> "
        mensaje += "y devolvió el siguiente error:\n\n<i>%s</i>."
        error ("Error inesperado", mensaje % (sys.exc_info () [0], sys.exc_info () [1]))
        return

    conexion.commit ()

def editar_celda_productos (celda, camino, texto, n_columna):
    if productos [camino] [n_columna] == texto: return

    if n_columna >= PRO_ESPESOR and n_columna <= PRO_LARGO or n_columna == PRO_PRECIO:
        try:
            productos [camino] [n_columna] = float (texto)
        except ValueError:
            informar ('Por favor, ingrese un valor numérico con coma opcional en esta columna', gtk.STOCK_DIALOG_ERROR)

    elif n_columna == PRO_FIGURA or n_columna == PRO_EXISTENCIA:
        try:
            figura = int (texto)
        except ValueError:
            informar ('Por favor, ingrese un valor entero en esta columna', gtk.STOCK_DIALOG_ERROR)
            return

        productos [camino] [n_columna] = figura

        if n_columna == PRO_FIGURA:
            global base, extension

            productos [camino] [n_columna] = int (texto)

            miniatura = '%s/%d.%s' % (base, figura, extension)

            if not os.path.exists (miniatura):
                miniatura = '%s/no.%s' % (base, extension)

            dibujo = gtk.gdk.pixbuf_new_from_file (ministura)
            #productos [camino] [PRO_MINIATURA] = dibujo.scale_simple (64, 64, gtk.gdk.INTERP_HYPER)
            productos [camino] [PRO_MINIATURA] = dibujo.scale_simple (32, 32, gtk.gdk.INTERP_HYPER)
    else:
        productos [camino] [n_columna] = texto

    sincronizar_datos (camino)

def editar_celda_items (celda, camino, texto, n_columna):
    if items [camino] [n_columna] == texto: return

    if n_columna == ITM_CANTIDAD:
        try:
            items [camino] [n_columna] = int (texto)
        except ValueError:
            informar ('Por favor, ingrese un valor entero en esta columna', gtk.STOCK_DIALOG_ERROR)
    else:
        try:
            items [camino] [n_columna] = float (texto)
        except ValueError:
            informar ('Por favor, ingrese un valor numérico con coma opcional en esta columna', gtk.STOCK_DIALOG_ERROR)

    cantidad                   = items [camino] [ITM_CANTIDAD]
    parcial                    = items [camino] [ITM_PARCIAL]
    items [camino] [ITM_TOTAL] = parcial * cantidad

    calcular_total ()

    #sincronizar_items (camino)

def obtener_color_gdk (color):
    color = re.match ('^#([0-9A-F]{4})([0-9A-F]{4})([0-9A-F]{4})', color)

    rojo  = color.group (1)
    verde = color.group (2)
    azul  = color.group (3)

    rojo  = int (rojo,  16)
    verde = int (verde, 16)
    azul  = int (azul,  16)

    color = gtk.gdk.Color (rojo, verde, azul)

    return color

def obtener_color_frente (estado):
    global configuracion
    if estado == 'PENDIENTE DE APROBACIÓN': return configuracion.colores.frente.pendiente
    if estado == 'PENDIENTE DE ENTREGA': return configuracion.colores.frente.aprobado
    if estado == 'CUMPLIDO': return configuracion.colores.frente.cumplido
    if estado == 'RECHAZADO': return configuracion.colores.frente.rechazado

def obtener_color_fondo (estado):
    global configuracion
    if estado == 'PENDIENTE DE APROBACIÓN': return configuracion.colores.fondo.pendiente
    if estado == 'PENDIENTE DE ENTREGA': return configuracion.colores.fondo.aprobado
    if estado == 'CUMPLIDO': return configuracion.colores.fondo.cumplido
    if estado == 'RECHAZADO': return configuracion.colores.fondo.rechazado

def obtener_fuente (estado):
    global configuracion
    if estado == 'PENDIENTE DE APROBACIÓN': return configuracion.fuentes.pendiente
    if estado == 'PENDIENTE DE ENTREGA': return configuracion.fuentes.aprobado
    if estado == 'CUMPLIDO': return configuracion.fuentes.cumplido
    if estado == 'RECHAZADO': return configuracion.fuentes.rechazado

def decrementar_existencia (numero):
    consulta  = 'select'
    consulta += ' i.espesor,'
    consulta += ' i.ancho,'
    consulta += ' i.largo,'
    consulta += ' i.figura,'
    consulta += ' i.cantidad'
    consulta += ' from items i where'
    consulta += ' i.presupuesto = %d' % int (numero)

    try:
        cursor.execute (consulta)
    except:
        mensaje  = "Mientras se ejecutaba la aplicación ocurrió un error inesperado con origen en la excepción <b>%s</b> "
        mensaje += "y devolvió el siguiente error:\n\n<i>%s</i>."
        error ("Error inesperado", mensaje % (sys.exc_info () [0], sys.exc_info () [1]))
        return

    for (
        espesor,
        ancho,
        largo,
        figura,
        cantidad
        ) in cursor.fetchall ():

        consulta  = 'update carbones set'
        consulta += ' existencia = existencia - %d' % int (cantidad)
        consulta += ' where'
        consulta += ' espesor = %.2f and' % float (espesor)
        consulta += ' ancho = %.2f and'   % float (ancho)
        consulta += ' largo = %.2f and'   % float (largo)
        consulta += ' figura = %d'        % int   (figura)

        try:
            cursor.execute (consulta)
        except:
            mensaje  = "Mientras se ejecutaba la aplicación ocurrió un error inesperado con origen en la excepción <b>%s</b> "
            mensaje += "y devolvió el siguiente error:\n\n<i>%s</i>."
            error ("Error inesperado", mensaje % (sys.exc_info () [0], sys.exc_info () [1]))
            return

    conexion.commit ()

def cambiar_estado (estado):
    seleccion     = interface.ventana.presupuestos.get_selection ()
    (dummy, iter) = seleccion.get_selected ()

    if not iter:
        informar ("Primero debe seleccionar el presupuesto.")
        return

    numero = presupuestos.get_value (iter, PRE_NUMERO)
    viejo  = presupuestos.get_value (iter, PRE_ESTADO)

    if viejo == 'PENDIENTE DE APROBACIÓN' and estado == 'PENDIENTE DE ENTREGA':
        mensaje = "Listo. Se aprobó el presupuesto %s" % str (numero).zfill (10)
    elif (viejo == 'PENDIENTE DE APROBACIÓN' or viejo == 'PENDIENTE DE ENTREGA') and estado == 'RECHAZADO':
        mensaje = "Listo. Se rechazó el presupuesto %s" % str (numero).zfill (10)
    elif viejo == 'PENDIENTE DE ENTREGA' and estado == 'CUMPLIDO':
        mensaje = "Listo. Se cumplió el presupuesto %s" % str (numero).zfill (10)
    else:
        informar ("No se puede cambiar el estado del presupuesto %s" % str (numero).zfill (10))
        return

    consulta  = "update presupuestos set "
    consulta += "estado = (select estado from estados where estado = '%s') " % estado
    consulta += "where "
    consulta += "numero = %d" % int (numero)

    frente = obtener_color_frente (estado)
    fondo  = obtener_color_fondo  (estado)
    fuente = obtener_fuente       (estado)

    presupuestos.set (iter,
            PRE_NUMERO,   numero,
            PRE_ESTADO,   estado,
            PRE_FRENTE,   frente,
            PRE_FONDO,    fondo,
            PRE_FUENTE,   fuente
        )

    try:
        cursor.execute (consulta)
    except:
        mensaje  = "Mientras se ejecutaba la aplicación ocurrió un error inesperado con origen en la excepción <b>%s</b> "
        mensaje += "y devolvió el siguiente error:\n\n<i>%s</i>."
        error ("Error inesperado", mensaje % (sys.exc_info () [0], sys.exc_info () [1]))
        return


    conexion.commit ()

    informar (mensaje)

class callbacks:
    def detener (*args):
        global detener
        detener = 1

    def presupuestar_producto (*args):
        iter = None

        seleccion     = interface.ventana.productos.get_selection ()
        (dummy, iter) = seleccion.get_selected ()

        if not iter:
            informar ('Debe seleccionar un producto de la lista')
            return

        miniatura = productos.get_value (iter, PRO_MINIATURA)
        espesor   = productos.get_value (iter, PRO_ESPESOR)
        ancho     = productos.get_value (iter, PRO_ANCHO)
        largo     = productos.get_value (iter, PRO_LARGO)
        figura    = productos.get_value (iter, PRO_FIGURA)
        precio    = productos.get_value (iter, PRO_PRECIO)

        detalle   = 'CARBÓN ELÉCTRICO DE '
        detalle  += '%.02fx'  % float (espesor)
        detalle  += '%.02fx'  % float (ancho)
        detalle  += '%.02f'   % float (largo)
        detalle  += ' (%d)'  % int   (figura)

        iter      = items.append ()
        cantidad  = 1

        items.set (iter,
                ITM_MINIATURA, miniatura,
                ITM_CANTIDAD,  int (cantidad),
                ITM_DETALLE,   detalle,
                ITM_ESPESOR,   float (espesor),
                ITM_ANCHO,     float (ancho),
                ITM_LARGO,     float (largo),
                ITM_FIGURA,    int (figura)
            )

        if precio:
            total = precio * cantidad
            items.set (iter, ITM_PARCIAL, precio)
            items.set (iter, ITM_TOTAL,   total)

        mensaje = 'Se agregó un %s al presupuesto' % detalle
        informar (mensaje, gtk.STOCK_DIALOG_INFO)
        calcular_total ()

    def presupuestar_carbon (*args):
        iter = None

        seleccionados = interface.ventana.carbones.get_selected_items ()
        if seleccionados:
            modelo = interface.ventana.carbones.get_model ()
            iter   = modelo.get_iter (seleccionados [0])
            if not iter:
                informar ('Debe seleccionar un carbón de la lista')
                return
        else:
            return

        dibujo    = carbones.get_value (iter, CAR_DIBUJO)
        #miniatura = dibujo.scale_simple (64, 64, gtk.gdk.INTERP_HYPER)
        miniatura = dibujo.scale_simple (32, 32, gtk.gdk.INTERP_HYPER)
        espesor   = carbones.get_value (iter, CAR_ESPESOR)
        ancho     = carbones.get_value (iter, CAR_ANCHO)
        largo     = carbones.get_value (iter, CAR_LARGO)
        figura    = carbones.get_value (iter, CAR_FIGURA)
        precio    = carbones.get_value (iter, CAR_PRECIO)

        detalle   = 'CARBÓN ELÉCTRICO DE '
        detalle  += '%.02fx'  % float (espesor)
        detalle  += '%.02fx'  % float (ancho)
        detalle  += '%.02f'   % float (largo)
        detalle  += ' (%d)'  % int   (figura)

        iter      = items.append ()
        cantidad  = 1

        items.set (iter,
                ITM_MINIATURA, miniatura,
                ITM_CANTIDAD,  cantidad,
                ITM_DETALLE,   detalle,
                ITM_ESPESOR,   espesor,
                ITM_ANCHO,     ancho,
                ITM_LARGO,     largo,
                ITM_FIGURA,    figura
            )

        if precio:
            total = precio * cantidad
            items.set (iter, ITM_PARCIAL, precio)
            items.set (iter, ITM_TOTAL,   total)

        mensaje = 'Se agregó un %s al presupuesto' % detalle
        informar (mensaje, gtk.STOCK_DIALOG_INFO)
        calcular_total ()

    def buscar_productos (*args):
        consulta  = 'select '
        consulta += 'p.catalogo,'
        consulta += 'p.marca,'
        consulta += 'p.codigo,'
        consulta += 'p.producto,'
        consulta += 'c.espesor,'
        consulta += 'c.ancho,'
        consulta += 'c.largo,'
        consulta += 'c.figura,'
        consulta += 'c.existencia,'
        consulta += 'c.precio'
        consulta += ' from carbones c, productos p where'
        consulta += ' p.espesor = c.espesor and'
        consulta += ' p.ancho   = c.ancho   and'
        consulta += ' p.largo   = c.largo   and'
        consulta += ' p.figura  = c.figura  and'

        catalogos      = interface.ventana.catalogo.get_model ()
        activo         = interface.ventana.catalogo.active
        marca          = interface.ventana.marca.child.get_text ()
        codigo         = interface.ventana.codigo.text
        producto       = interface.ventana.producto.text
        try:
            catalogo       = catalogos [activo] [0]
        except:
            informar ("No se encontró ningún catálogo")
            return

        if not catalogo == TODOS: consulta += " upper (p.catalogo) = upper ('%s') and"      % catalogo
        if marca:                  consulta += " upper (p.marca)    like upper ('%s%%') and" % marca
        if codigo:                 consulta += " upper (p.codigo)   = upper ('%s') and"      % codigo
        if producto:               consulta += " upper (p.producto) like upper ('%s%%') and" % producto

        consulta += ' 1 = 1'
        consulta += ' order by'
        consulta += ' p.catalogo,'
        consulta += ' p.marca,'
        consulta += ' p.producto'

        productos.clear ()

        try:
                cursor.execute (consulta)
        except:
                mensaje  = "Mientras se ejecutaba la aplicación ocurrió un error inesperado con origen en la excepción <b>%s</b> "
                mensaje += "y devolvió el siguiente error:\n\n<i>%s</i>."
                error ("Error inesperado", mensaje % (sys.exc_info () [0], sys.exc_info () [1]))
                return

        resultado   = cursor.fetchall ()
        encontrados = len (resultado)
        registro    = 0

        interface.ventana.progreso.show ()
        interface.ventana.detener.show ()
        while gtk.events_pending (): gtk.main_iteration ()

        global detener, base, extension
        detener = 0

        for (
                catalogo,
                marca,
                codigo,
                producto,
                espesor,
                ancho,
                largo,
                figura,
                existencia,
                precio,
            ) in resultado:

            if detener: break

            registro   += 1
            fraccion    = float (registro) / float (encontrados)
            porcentaje  = fraccion * 100
            informar ('Recuperando registro %d de %d' % (registro, encontrados))
            interface.ventana.progreso.set_text ('%d%%' % porcentaje)
            interface.ventana.progreso.set_fraction (fraccion)
            while gtk.events_pending (): gtk.main_iteration ()

            recortada = '%s/%d.%s' % (base, figura, extension)

            if not os.path.exists (recortada): recortada = '%s/no.%s' % (base, extension)

            dibujo    = gtk.gdk.pixbuf_new_from_file (recortada)
            dibujo    = dibujo.scale_simple (115, 115, gtk.gdk.INTERP_HYPER)
            #miniatura = dibujo.scale_simple (64, 64, gtk.gdk.INTERP_HYPER)
            miniatura = dibujo.scale_simple (32, 32, gtk.gdk.INTERP_HYPER)
            iter      = productos.append ()
            camino    = productos.get_path (iter)

            if existencia: productos.set (iter, PRO_EXISTENCIA, existencia)
            if precio: productos.set (iter, PRO_PRECIO, precio)

            productos.set (iter,
                    PRO_MINIATURA,    miniatura,
                    PRO_CATALOGO,     catalogo,
                    PRO_MARCA,        marca,
                    PRO_CODIGO,       codigo,
                    PRO_PRODUCTO,     producto,
                    PRO_ESPESOR,      espesor,
                    PRO_ANCHO,        ancho,
                    PRO_LARGO,        largo,
                    PRO_FIGURA,       figura
                )

        if not encontrados:
            informar ("No se encontró ningún producto con las características especificadas", gtk.STOCK_DIALOG_WARNING)
        elif encontrados > 1:
            if detener:
                if not registro:
                    informar ("Operación cancelada. No se muestra ningún producto de los %d encontrados" % (encontrados), gtk.STOCK_DIALOG_WARNING)
                elif registro > 1:
                    informar ("Operación cancelada. Se muestran sólo %d productos de los %d encontrados" % (registro, encontrados), gtk.STOCK_DIALOG_WARNING)
                else:
                    informar ("Operación cancelada. Se muestra un sólo producto de los %d encontrados" % (encontrados), gtk.STOCK_DIALOG_WARNING)
            else:
                informar ("Se encontraron %d productos" % encontrados, gtk.STOCK_DIALOG_INFO)
        else:
            informar ("Se encontró un sólo producto", gtk.STOCK_DIALOG_INFO)

        interface.ventana.progreso.hide ()
        interface.ventana.detener.hide ()

    def buscar_carbones (*args):
        consulta  = 'select '
        consulta += 'c.espesor,'
        consulta += 'c.ancho,'
        consulta += 'c.largo,'
        consulta += 'c.figura,'
        consulta += 'c.existencia,'
        consulta += 'c.precio'
        consulta += ' from carbones c where'

        espesor_minimo   = interface.ventana.espesor_minimo.text.replace (',', '.')
        ancho_minimo     = interface.ventana.ancho_minimo.text.replace (',', '.')
        largo_minimo     = interface.ventana.largo_minimo.text.replace (',', '.')
        figura_minima    = interface.ventana.figura_minima.text.replace (',', '.')
        espesor_maximo   = interface.ventana.espesor_maximo.text.replace (',', '.')
        ancho_maximo     = interface.ventana.ancho_maximo.text.replace (',', '.')
        largo_maximo     = interface.ventana.largo_maximo.text.replace (',', '.')
        figura_maxima    = interface.ventana.figura_maxima.text.replace (',', '.')
        faltante         = interface.ventana.faltante.active
        buscar_productos = interface.ventana.buscar_productos.active

        if not espesor_maximo: espesor_maximo = espesor_minimo
        if not ancho_maximo:   ancho_maximo   = ancho_minimo
        if not largo_maximo:   largo_maximo   = largo_minimo
        if not figura_maxima:  figura_maxima  = figura_minima

        if espesor_minimo:
            try:
                consulta += ' c.espesor between %.2f and %.2f and' % (float (espesor_minimo), float (espesor_maximo))
            except ValueError:
                informar ("Ingrese un espesor válido", gtk.STOCK_DIALOG_ERROR)
                interface.ventana.progreso.hide ()
                return

        if ancho_minimo:
            try:
                consulta += ' c.ancho between %.2f and %.2f and' % (float (ancho_minimo), float (ancho_maximo))
            except ValueError:
                informar ("Ingrese un ancho válido")
                interface.ventana.progreso.hide ()
                return

        if largo_minimo:
            try:
                consulta += ' c.largo between %.2f and %.2f and' % (float (largo_minimo), float (largo_maximo))
            except ValueError:
                informar ("Ingrese un largo válido")
                interface.ventana.progreso.hide ()
                return

        if figura_minima:
            try:
                consulta += ' c.figura between %d and %d and' % (int (figura_minima), int (figura_maxima))
            except ValueError:
                informar ("Ingrese una figura válida")
                interface.ventana.progreso.hide ()
                return

        if faltante:
            global configuracion
            minimo = configuracion.minimo
            consulta += '  c.existencia <= %d and' % int (minimo)

        consulta += ' 1 = 1'

        carbones.clear ()

        try:
                cursor.execute (consulta)
        except:
                mensaje  = "Mientras se ejecutaba la aplicación ocurrió un error inesperado con origen en la excepción <b>%s</b> "
                mensaje += "y devolvió el siguiente error:\n\n<i>%s</i>."
                error ("Error inesperado", mensaje % (sys.exc_info () [0], sys.exc_info () [1]))
                return

        resultado   = cursor.fetchall ()
        encontrados = len (resultado)
        registro    = 0

        interface.ventana.progreso.show ()
        interface.ventana.detener.show ()
        while gtk.events_pending (): gtk.main_iteration ()

        global detener, base, extension
        detener = 0

        for (
                espesor,
                ancho,
                largo,
                figura,
                existencia,
                precio,
            ) in resultado:

            if detener: break

            registro   += 1
            fraccion    = float (registro) / float (encontrados)
            fraccion   /= buscar_productos and 2 or 1
            porcentaje  = fraccion * 100
            informar ('Recuperando registro %d de %d' % (registro, encontrados))
            interface.ventana.progreso.set_text ('%d%%' % porcentaje)
            interface.ventana.progreso.set_fraction (fraccion)
            while gtk.events_pending (): gtk.main_iteration ()

            recortada = '%s/%d.%s' % (base, figura, extension)

            if not os.path.exists (recortada):
                recortada = '%s/no.%s' % (base, extension)

            dibujo    = gtk.gdk.pixbuf_new_from_file (recortada)
            dibujo    = dibujo.scale_simple (115, 115, gtk.gdk.INTERP_HYPER)
            iter      = carbones.append ()
            camino    = carbones.get_path (iter)

            if existencia: carbones.set (iter, CAR_EXISTENCIA, existencia)
            if precio: carbones.set (iter, CAR_PRECIO, precio)

            carbones.set (iter,
                    CAR_DIBUJO,       dibujo,
                    CAR_ESPESOR,      espesor,
                    CAR_ANCHO,        ancho,
                    CAR_LARGO,        largo,
                    CAR_FIGURA,       figura
                )

            descripcion = obtener_descripcion (camino)
            carbones.set (iter, CAR_DESCRIPCION, descripcion)

        if not encontrados:
            informar ("No se encontró ningún carbón con las dimensiones especificadas", gtk.STOCK_DIALOG_WARNING)
        elif encontrados > 1:
            if detener:
                if not registro:
                    informar ("Operación cancelada. No se muestra ningún carbón de los %d encontrados" % (encontrados), gtk.STOCK_DIALOG_WARNING)
                elif registro > 1:
                    informar ("Operación cancelada. Se muestran sólo %d carbones de los %d encontrados" % (registro, encontrados), gtk.STOCK_DIALOG_WARNING)
                else:
                    informar ("Operación cancelada. Se muestra un sólo carbón de los %d encontrados" % (encontrados), gtk.STOCK_DIALOG_WARNING)
            else:
                informar ("Se encontraron %d carbones" % encontrados, gtk.STOCK_DIALOG_INFO)
        else:
            informar ("Se encontró un sólo carbón", gtk.STOCK_DIALOG_INFO)

        if buscar_productos:
            consulta  = 'select '
            consulta += 'p.catalogo,'
            consulta += 'p.marca,'
            consulta += 'p.codigo,'
            consulta += 'p.producto,'
            consulta += 'c.espesor,'
            consulta += 'c.ancho,'
            consulta += 'c.largo,'
            consulta += 'c.figura,'
            consulta += 'c.existencia,'
            consulta += 'c.precio'
            consulta += ' from carbones c, productos p where'
            consulta += ' p.espesor = c.espesor and'
            consulta += ' p.ancho   = c.ancho   and'
            consulta += ' p.largo   = c.largo   and'
            consulta += ' p.figura  = c.figura  and'

            if espesor_minimo: consulta += ' c.espesor between %.2f and %.2f and' % (float (espesor_minimo), float (espesor_maximo))
            if ancho_minimo:   consulta += ' c.ancho between %.2f and %.2f and'   % (float (ancho_minimo),   float (ancho_maximo))
            if largo_minimo:   consulta += ' c.largo between %.2f and %.2f and'   % (float (largo_minimo),   float (largo_maximo))
            if figura_minima:  consulta += ' c.figura between %d and %d and'      % (int   (figura_minima),  int   (figura_maxima))
            if faltante:       consulta += ' c.existencia <= %d and'              % int (minimo)

            consulta += ' 1 = 1'
            consulta += ' order by'
            consulta += ' p.catalogo,'
            consulta += ' p.marca,'
            consulta += ' p.producto'

            productos.clear ()

            try:
                cursor.execute (consulta)
            except:
                mensaje  = "Mientras se ejecutaba la aplicación ocurrió un error inesperado con origen en la excepción <b>%s</b> "
                mensaje += "y devolvió el siguiente error:\n\n<i>%s</i>."
                error ("Error inesperado", mensaje % (sys.exc_info () [0], sys.exc_info () [1]))
                return

            carbones_encontrados = encontrados
            resultado            = cursor.fetchall ()
            encontrados          = len (resultado)
            registro             = 0

            for (
                    catalogo,
                    marca,
                    codigo,
                    producto,
                    espesor,
                    ancho,
                    largo,
                    figura,
                    existencia,
                    precio,
                ) in resultado:

                if detener: break

                registro   += 1
                fraccion    = float (registro) / float (encontrados)
                fraccion   /= 2
                fraccion   += 0.50
                porcentaje  = fraccion * 100
                informar ('Recuperando registro %d de %d' % (registro, encontrados))
                interface.ventana.progreso.set_text ('%d%%' % porcentaje)
                interface.ventana.progreso.set_fraction (fraccion)
                while gtk.events_pending (): gtk.main_iteration ()

                recortada = '%s/%d.%s' % (base, figura, extension)

                if not os.path.exists (recortada): recortada = '%s/no.%s' % (base, extension)

                dibujo    = gtk.gdk.pixbuf_new_from_file (recortada)
                dibujo    = dibujo.scale_simple (115, 115, gtk.gdk.INTERP_HYPER)
                #miniatura = dibujo.scale_simple (64, 64, gtk.gdk.INTERP_HYPER)
                miniatura = dibujo.scale_simple (32, 32, gtk.gdk.INTERP_HYPER)
                iter      = productos.append ()
                camino    = productos.get_path (iter)

                if existencia: productos.set (iter, PRO_EXISTENCIA, existencia)
                if precio: productos.set (iter, PRO_PRECIO, precio)

                productos.set (iter,
                        PRO_MINIATURA,    miniatura,
                        PRO_CATALOGO,     catalogo,
                        PRO_MARCA,        marca,
                        PRO_CODIGO,       codigo,
                        PRO_PRODUCTO,     producto,
                        PRO_ESPESOR,      espesor,
                        PRO_ANCHO,        ancho,
                        PRO_LARGO,        largo,
                        PRO_FIGURA,       figura
                    )

            mensaje = carbones_encontrados > 1 and 'los %d carbones especificados' % carbones_encontrados or 'el carbón especificado'

            if not encontrados:
                informar ("No se encontró ningún producto para %s" % mensaje, gtk.STOCK_DIALOG_WARNING)
            elif encontrados > 1:
                if detener:
                    if not registro:
                        informar ("Operación cancelada. No se muestra ningún producto de %d encontrados para %s" % (encontrados, mensaje), gtk.STOCK_DIALOG_WARNING)
                    elif registro > 1:
                        informar ("Operación cancelada. Se muestran sólo %d productos de %d encontrados para %s" % (registro, encontrados, mensaje), gtk.STOCK_DIALOG_WARNING)
                    else:
                        informar ("Operación cancelada. Se muestra un sólo productos de los %d encontrados para %s" % (encontrados, mensaje), gtk.STOCK_DIALOG_WARNING)
                else:
                    informar ("Se encontraron %d productos para %s" % (encontrados, mensaje), gtk.STOCK_DIALOG_INFO)
            else:
                informar ("Se encontró un sólo producto para %s" % mensaje, gtk.STOCK_DIALOG_INFO)

        interface.ventana.progreso.hide ()
        interface.ventana.detener.hide ()

    def buscar_presupuestos (*args):
        consulta  = 'select '
        consulta += 'p.numero,'
        consulta += 'p.fecha,'
        consulta += 'p.contacto,'
        consulta += 'e.estado,'
        consulta += 'p.total'
        consulta += ' from presupuestos p, estados e where'
        consulta += ' p.estado = e.estado and'

        numero_desde = interface.ventana.numero_desde.text.replace (',', '.')
        numero_hasta = interface.ventana.numero_hasta.text.replace (',', '.')
        fecha_desde  = interface.ventana.fecha_desde.text.replace (',', '.')
        fecha_hasta  = interface.ventana.fecha_hasta.text.replace (',', '.')
        estados      = interface.ventana.estado.get_model ()
        activo       = interface.ventana.estado.active
        try:
            estado = estados [activo] [0]
        except:
            informar ("No se encontró ningún estado")
            return

        if not numero_hasta: numero_hasta = numero_desde
        if not fecha_hasta:  fecha_hasta  = fecha_desde

        if numero_desde:
            try:
                consulta += ' p.numero between %d and %d and' % (int (numero_desde), int (numero_hasta))
            except ValueError:
                informar ("Ingrese un número válido")
                interface.ventana.progreso.hide ()
                return

        if fecha_desde:
            try:
                (dia, mes, ano) = fecha_desde.split ('/')
                date (int (ano), int (mes), int (dia))
                (dia, mes, ano) = fecha_hasta.split ('/')
                date (int (ano), int (mes), int (dia))
                consulta += " p.fecha between '%s' and '%s' and" % (fecha_desde, fecha_hasta)
            except ValueError:
                informar ("Ingrese una fecha válida")
                interface.ventana.progreso.hide ()
                return

        if not estado == 'TODOS LOS ESTADOS': consulta += " upper (p.estado) = upper ('%s') and" % estado

        # Cerrar la cláusula con una condición verdadera
        consulta += ' 1 = 1'

        consulta += ' order by'
        consulta += ' p.fecha,'
        consulta += ' p.numero,'
        consulta += ' e.estado'

        presupuestos.clear ()

        try:
            cursor.execute (consulta)
        except:
            mensaje  = "Mientras se ejecutaba la aplicación ocurrió un error inesperado con origen en la excepción <b>%s</b> "
            mensaje += "y devolvió el siguiente error:\n\n<i>%s</i>."
            error ("Error inesperado", mensaje % (sys.exc_info () [0], sys.exc_info () [1]))
            return

        resultado   = cursor.fetchall ()
        encontrados = len (resultado)
        registro    = 0

        interface.ventana.progreso.show ()
        interface.ventana.detener.show ()
        while gtk.events_pending (): gtk.main_iteration ()

        global detener
        detener = 0

        for (
                numero,
                fecha,
                contacto,
                estado,
                total,
            ) in resultado:

            if detener: break

            registro   += 1
            fraccion    = float (registro) / float (encontrados)
            porcentaje  = fraccion * 100
            informar ('Recuperando registro %d de %d' % (registro, encontrados))
            interface.ventana.progreso.set_text ('%d%%' % porcentaje)
            interface.ventana.progreso.set_fraction (fraccion)
            while gtk.events_pending (): gtk.main_iteration ()

            iter      = presupuestos.append ()

            if contacto == '': contacto = 'ANÓNIMO'

            frente = obtener_color_frente (estado)
            fondo  = obtener_color_fondo  (estado)
            fuente = obtener_fuente       (estado)

            presupuestos.set (iter,
                    PRE_NUMERO,   numero,
                    PRE_FECHA,    fecha,
                    PRE_CONTACTO, contacto,
                    PRE_ESTADO,   estado,
                    PRE_TOTAL,    total,
                    PRE_FRENTE,   frente,
                    PRE_FONDO,    fondo,
                    PRE_FUENTE,   fuente
                )

        if not encontrados:
            informar ("No se encontró ningún presupuesto con las características especificadas", gtk.STOCK_DIALOG_WARNING)
        elif encontrados > 1:
            if detener:
                if not registro:
                    informar ("Operación cancelada. No se muestra ningún presupuesto de los %d encontrados" % (encontrados), gtk.STOCK_DIALOG_WARNING)
                elif registro > 1:
                    informar ("Operación cancelada. Se muestran sólo %d presupuestos de los %d encontrados" % (registro, encontrados), gtk.STOCK_DIALOG_WARNING)
                else:
                    informar ("Operación cancelada. Se muestra un sólo %d presupuesto de los %d encontrados" % (encontrados), gtk.STOCK_DIALOG_WARNING)
            else:
                informar ("Se encontraron %d presupuestos" % encontrados, gtk.STOCK_DIALOG_INFO)
        else:
            informar ("Se encontró un sólo presupuesto", gtk.STOCK_DIALOG_INFO)

        interface.ventana.progreso.hide ()
        interface.ventana.detener.hide ()

    def limpiar (*args):
        informar (None)

        carbones.clear ()
        productos.clear ()

        interface.ventana.espesor_minimo.set_text ('')
        interface.ventana.ancho_minimo.set_text   ('')
        interface.ventana.largo_minimo.set_text   ('')
        interface.ventana.figura_minima.set_text  ('')
        interface.ventana.espesor_maximo.set_text ('')
        interface.ventana.ancho_maximo.set_text   ('')
        interface.ventana.largo_maximo.set_text   ('')
        interface.ventana.figura_maxima.set_text  ('')
        interface.ventana.marca.child.set_text    ('')
        interface.ventana.codigo.set_text         ('')
        interface.ventana.producto.set_text       ('')
        interface.ventana.catalogo.set_active     (0)

    def limpiar_presupuestos (*args):
        informar (None)

        presupuestos.clear ()

        interface.ventana.numero_desde.set_text ('')
        interface.ventana.numero_hasta.set_text ('')
        interface.ventana.fecha_desde.set_text  ('')
        interface.ventana.fecha_hasta.set_text  ('')
        interface.ventana.estado.set_active     (0)

    def mostrar_figura (*args):
        global base, extension

        interface.formulario.nueva.set_sensitive (False)

        figura    = interface.formulario.figura.get_value ()
        recortada = '%s/%d.%s' % (base, figura, extension)

        if not os.path.exists (recortada):
            recortada = '%s/no.%s' % (base, extension)
            interface.formulario.nueva.set_sensitive (True)

        dibujo = gtk.gdk.pixbuf_new_from_file (recortada)
        interface.formulario.dibujo.set_from_pixbuf (dibujo)

    def agregar_figura (*args):
        figura = interface.formulario.figura.get_value ()

        try:
            figura = int (figura)
        except ValueError:
            return

        selector = gtk.FileChooserDialog ('Por favor, seleccione la figura',
                interface.formulario,
                gtk.FILE_CHOOSER_ACTION_OPEN,
                    (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
                     gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)
            )

        respuesta = selector.run ()
        archivo = selector.get_filename ()
        selector.destroy ()

        if respuesta == gtk.RESPONSE_ACCEPT:
            global base, extension

            if not archivo.endswith ('.%s' % extension):
                error ('Formato incompatible', 'El formato del archivo de imagen no es compatible, por favor, conviértalo a fomato %s.' % extension.upper ())
            else:
                origen  = open (archivo, 'rb')
                destino = open ('%s/%d.%s' % (base, figura, extension), 'wb')
                destino.write (origen.read ())
                destino.close ()
                origen.close ()

        interface.formulario.figura.emit ('value-changed')

    def aprobar (*args):
        cambiar_estado ('PENDIENTE DE ENTREGA')

    def rechazar (*args):
        cambiar_estado ('RECHAZADO')

    def cumplir (*args):
        cambiar_estado ('CUMPLIDO')

    def hoy (*args):
        interface.ventana.fecha_desde.set_text (fecha)

    def quitar_carbon (*args):
        iter = None

        items = interface.ventana.carbones.get_selected_items ()
        if items:
            modelo = interface.ventana.carbones.get_model ()
            iter   = modelo.get_iter (items [0])

        if not iter:
            informar ('Debe seleccionar un carbón de la lista')
            return

        if preguntar ("¿Está seguro?", 'Esta operación quitará para siempre el carbón del catálogo y se perderán todos los productos que lo soportan.'):
            espesor  = carbones.get_value (iter, CAR_ESPESOR)
            ancho    = carbones.get_value (iter, CAR_ANCHO)
            largo    = carbones.get_value (iter, CAR_LARGO)
            figura   = carbones.get_value (iter, CAR_FIGURA)

            consulta  = 'delete from carbones where '
            consulta += 'espesor = %.2f and ' % espesor
            consulta += 'ancho   = %.2f and ' % ancho
            consulta += 'largo   = %.2f and ' % largo
            consulta += 'figura  = %d;'       % figura

            try:
                cursor.execute (consulta)
            except:
                mensaje  = "Mientras se ejecutaba la aplicación ocurrió un error inesperado con origen en la excepción <b>%s</b> "
                mensaje += "y devolvió el siguiente error:\n\n<i>%s</i>."
                error ("Error inesperado", mensaje % (sys.exc_info () [0], sys.exc_info () [1]))
                return

            consulta  = 'delete from productos where '
            consulta += 'espesor = %.2f and ' % espesor
            consulta += 'ancho   = %.2f and ' % ancho
            consulta += 'largo   = %.2f and ' % largo
            consulta += 'figura  = %d'        % figura

            try:
                cursor.execute (consulta)
            except:
                mensaje  = "Mientras se ejecutaba la aplicación ocurrió un error inesperado con origen en la excepción <b>%s</b> "
                mensaje += "y devolvió el siguiente error:\n\n<i>%s</i>."
                error ("Error inesperado", mensaje % (sys.exc_info () [0], sys.exc_info () [1]))
                return

            carbones.remove (iter)
            conexion.commit ()
            informar ('Listo, se quitó el carbón del catálogo', gtk.STOCK_DIALOG_INFO)

    def quitar_producto (*args):
        iter = None

        seleccion      = interface.ventana.productos.get_selection ()
        (dummy, iter)  = seleccion.get_selected ()

        if not iter:
            informar ('Debe seleccionar un producto de la lista')
            return

        if preguntar ('¿Está seguro?', 'Esta operación eliminará el producto del catálogo (no afecta al carbón).'):

            catalogo = productos.get_value (iter, COL_CATALOGO)
            marca    = productos.get_value (iter, COL_MARCA)
            producto = productos.get_value (iter, COL_PRODUCTO)

            consulta  = 'delete from productos where '
            consulta += "catalogo = '%s' and "    % catalogo
            consulta += "marca    = '%s' and "    % marca
            consulta += "producto = '%s' and "    % producto

            try:
                cursor.execute (consulta)
            except:
                mensaje  = "Mientras se ejecutaba la aplicación ocurrió un error inesperado con origen en la excepción <b>%s</b> "
                mensaje += "y devolvió el siguiente error:\n\n<i>%s</i>."
                error ("Error inesperado", mensaje % (sys.exc_info () [0], sys.exc_info () [1]))
                return

            productos.remove (iter)
            conexion.commit ()
            informar ('Listo, se quitó el producto del catálogo', gtk.STOCK_DIALOG_INFO)

    def agregar_carbon (*args):
        interface.formulario.espesor.set_text     ('')
        interface.formulario.ancho.set_text       ('')
        interface.formulario.largo.set_text       ('')
        interface.formulario.figura.set_value     (0)
        interface.formulario.existencia.set_text  ('')
        interface.formulario.precio.set_text      ('')
        interface.formulario.marca.child.set_text ('')
        interface.formulario.codigo.set_text      ('')
        interface.formulario.producto.set_text    ('')
        interface.formulario.catalogo.set_active  (0)
        interface.formulario.nueva.set_sensitive  (False)

        interface.formulario.figura.emit ('value-changed')

        interface.formulario.marco_producto.hide ()

        while True:
            interface.formulario.set_transient_for (interface.ventana)
            respuesta = interface.formulario.run ()

            if not respuesta == gtk.RESPONSE_OK: break

            espesor    = interface.formulario.espesor.text.replace (',', '.')
            ancho      = interface.formulario.ancho.text.replace   (',', '.')
            largo      = interface.formulario.largo.text.replace   (',', '.')
            figura     = interface.formulario.figura.get_value ()
            existencia = interface.formulario.existencia.text.replace (',', '.')
            precio     = interface.formulario.precio.text.replace     (',', '.')

            consulta  = 'insert into carbones ('
            consulta += 'espesor,'
            consulta += 'ancho,'
            consulta += 'largo,'
            consulta += 'figura,'
            consulta += 'existencia,'
            consulta += 'precio'
            consulta += ') values ('

            try:
                consulta += " %.2f," % float (espesor)
            except ValueError:
                error ('Algunos datos son incorrectos', 'Por favor, ingrese un espesor válido')
                interface.formulario.espesor.grab_focus ()
                continue

            try:
                consulta += " %.2f," % float (ancho)
            except ValueError:
                error ('Algunos datos son incorrectos', 'Por favor, ingrese un ancho válido')
                interface.formulario.ancho.grab_focus ()
                continue

            try:
                consulta += " %.2f," % float (largo)
            except ValueError:
                error ('Algunos datos son incorrectos', 'Por favor, ingrese un largo válido')
                interface.formulario.largo.grab_focus ()
                continue

            try:
                consulta += " %d," % int (figura)
            except ValueError:
                error ('Algunos datos son incorrectos', 'Por favor, ingrese una figura válida')
                interface.formulario.figura.grab_focus ()
                continue

            try:
                consulta += " %d," % int (existencia)
            except ValueError:
                consulta += " null,"

            try:
                consulta += " %d," % float (precio)
            except ValueError:
                consulta += " null"

            consulta += ')'

            try:
                cursor.execute (consulta)
            except sql.IntegrityError:
                mensaje = 'El carbón no se agregó porque ya existe'
                break
            except:
                mensaje  = "Mientras se ejecutaba la aplicación ocurrió un error inesperado con origen en la excepción <b>%s</b> "
                mensaje += "y devolvió el siguiente error:\n\n<i>%s</i>."
                error ("Error inesperado", mensaje % (sys.exc_info () [0], sys.exc_info () [1]))
                return

            conexion.commit ()
            informar ('Listo, se agregó un nuevo carbón al catálogo', gtk.STOCK_DIALOG_INFO)
            break

        interface.formulario.hide ()

    def agregar_producto (*args):
        interface.formulario.espesor.set_text     ('')
        interface.formulario.ancho.set_text       ('')
        interface.formulario.largo.set_text       ('')
        interface.formulario.figura.set_value     (0)
        interface.formulario.existencia.set_text  ('')
        interface.formulario.precio.set_text      ('')
        interface.formulario.marca.child.set_text ('')
        interface.formulario.codigo.set_text      ('')
        interface.formulario.producto.set_text    ('')
        interface.formulario.catalogo.set_active  (0)
        interface.formulario.nueva.set_sensitive  (False)

        interface.formulario.figura.emit ('value-changed')

        interface.formulario.marco_producto.show ()

        while True:
            interface.formulario.set_transient_for (interface.ventana)
            respuesta = interface.formulario.run ()

            if not respuesta == gtk.RESPONSE_OK: break

            espesor    = interface.formulario.espesor.text.replace (',', '.')
            ancho      = interface.formulario.ancho.text.replace   (',', '.')
            largo      = interface.formulario.largo.text.replace   (',', '.')
            figura     = interface.formulario.figura.get_value ()
            existencia = interface.formulario.existencia.text.replace (',', '.')
            precio     = interface.formulario.precio.text.replace     (',', '.')

            consulta  = 'insert into carbones ('
            consulta += 'espesor,'
            consulta += 'ancho,'
            consulta += 'largo,'
            consulta += 'figura,'
            consulta += 'existencia,'
            consulta += 'precio'
            consulta += ') values ('

            try:
                consulta += " %.2f," % float (espesor)
            except ValueError:
                error ('Algunos datos son incorrectos', 'Por favor, ingrese un espesor válido')
                interface.formulario.espesor.grab_focus ()
                continue

            try:
                consulta += " %.2f," % float (ancho)
            except ValueError:
                error ('Algunos datos son incorrectos', 'Por favor, ingrese un ancho válido')
                interface.formulario.ancho.grab_focus ()
                continue

            try:
                consulta += " %.2f," % float (largo)
            except ValueError:
                error ('Algunos datos son incorrectos', 'Por favor, ingrese un largo válido')
                interface.formulario.largo.grab_focus ()
                continue

            try:
                consulta += " %d," % int (figura)
            except ValueError:
                error ('Algunos datos son incorrectos', 'Por favor, ingrese una figura válida')
                interface.formulario.figura.grab_focus ()
                continue

            try:
                consulta += " %d," % int (existencia)
            except ValueError:
                consulta += " null,"

            try:
                consulta += " %d," % float (precio)
            except ValueError:
                consulta += " null"

            consulta += ')'

            try:
                cursor.execute (consulta)
            except sql.IntegrityError:
                pass
            except:
                mensaje  = "Mientras se ejecutaba la aplicación ocurrió un error inesperado con origen en la excepción <b>%s</b> "
                mensaje += "y devolvió el siguiente error:\n\n<i>%s</i>."
                error ("Error inesperado", mensaje % (sys.exc_info () [0], sys.exc_info () [1]))
                return

            catalogos  = interface.formulario.catalogo.get_model ()
            activo     = interface.formulario.catalogo.active
            marca      = interface.formulario.marca.child.get_text ()
            codigo     = interface.formulario.codigo.text
            producto   = interface.formulario.producto.text
            try:
                catalogo = catalogos [activo] [0]
            except:
                informar ("No se encontró ningún catálogo")
                return

            if marca == '':
                error ('Debe completar todos los datos', 'Por favor, ingrese la marca del producto')
                interface.formulario.marca.grab_focus ()
                continue

            if producto == '':
                error ('Debe completar todos los datos', 'Por favor, ingrese la descripción del producto')
                interface.formulario.producto.grab_focus ()
                continue

            consulta  = 'insert into productos ('
            consulta += 'catalogo,'
            consulta += 'marca,'
            consulta += 'producto,'
            consulta += 'espesor,'
            consulta += 'ancho,'
            consulta += 'largo,'
            consulta += 'figura'
            consulta += ') values ('
            consulta += "upper ('%s')," % catalogo
            consulta += "upper ('%s')," % marca
            consulta += "upper ('%s')," % producto
            consulta += "%.2f,"         % float (espesor)
            consulta += "%.2f,"         % float (ancho)
            consulta += "%.2f,"         % float (largo)
            consulta += "%d"            % int   (figura)
            consulta += ");"

            try:
                cursor.execute (consulta)
            except sql.IntegrityError:
                mensaje = 'El producto no se agregó porque ya existe'
                break
            except:
                mensaje  = "Mientras se ejecutaba la aplicación ocurrió un error inesperado con origen en la excepción <b>%s</b> "
                mensaje += "y devolvió el siguiente error:\n\n<i>%s</i>."
                error ("Error inesperado", mensaje % (sys.exc_info () [0], sys.exc_info () [1]))
                return

            conexion.commit ()
            informar ('Listo, se agregó un nuevo producto', gtk.STOCK_DIALOG_INFO)
            break

        interface.formulario.hide ()

    def filtrar_marcas (*args):
        interface.ventana.marca.child.set_text ('')

        marcas = gtk.ListStore (str)
        interface.ventana.marca.set_model (marcas)
        interface.ventana.marca.set_text_column (0)

        catalogos = interface.ventana.catalogo.get_model ()
        activo    = interface.ventana.catalogo.get_active ()
        try:
            catalogo = catalogos [activo] [0]
        except:
            informar ("No se encontró ningún catálogo")
            return

        if catalogo == TODOS:
            marcas.clear ()
            return

        consulta  = "select marca from productos where catalogo = '"
        consulta += catalogo
        consulta += "' group by marca order by marca"

        try:
            cursor.execute (consulta)
        except:
            mensaje  = "Mientras se ejecutaba la aplicación ocurrió un error inesperado con origen en la excepción <b>%s</b> "
            mensaje += "y devolvió el siguiente error:\n\n<i>%s</i>."
            error ("Error inesperado", mensaje % (sys.exc_info () [0], sys.exc_info () [1]))
            return

        for marca, in cursor:
            interface.ventana.marca.append_text (marca)

    def filtrar_marcas_formulario (*args):
        interface.formulario.marca.child.set_text ('')

        marcas = gtk.ListStore (str)
        interface.formulario.marca.set_model (marcas)
        interface.formulario.marca.set_text_column (0)

        catalogos = interface.formulario.catalogo.get_model ()
        activo    = interface.formulario.catalogo.get_active ()
        if not list (catalogos): return
        catalogo  = catalogos [activo] [0]

        if catalogo == TODOS:
            marcas.clear ()
            return

        consulta  = "select marca from productos where catalogo = '"
        consulta += catalogo
        consulta += "' group by marca order by marca"

        try:
            cursor.execute (consulta)
        except:
            mensaje  = "Mientras se ejecutaba la aplicación ocurrió un error inesperado con origen en la excepción <b>%s</b> "
            mensaje += "y devolvió el siguiente error:\n\n<i>%s</i>."
            error ("Error inesperado", mensaje % (sys.exc_info () [0], sys.exc_info () [1]))
            return

        for marca, in cursor:
            interface.formulario.marca.append_text (marca)

    def guardar (*args):
        numero   = interface.ventana.numero.label
        contacto = interface.ventana.para.child.get_text ()
        total    = interface.ventana.total.text.replace (',', '.')

        numero = int (re.match ('<b>0*([0-9]+)<\/b>', numero).group (1))

        if not list (items):
            informar ("No se puede guardar un presupuesto sin productos a presupuestar", gtk.STOCK_DIALOG_ERROR)
            return

        consulta  = "insert into presupuestos ("
        consulta += "numero,"
        consulta += "fecha,"
        consulta += "contacto,"
        consulta += "estado,"
        consulta += "total"
        consulta += ") select "
        consulta += "%d,"      % int (numero)
        consulta += "'%s',"    % fecha
        consulta += "'%s',"    % contacto
        consulta += "estado,"

        try:
            consulta += "%.2f" % float (total)
        except ValueError:
            informar ("El precio total no es válido, el presupuesto no se guardó", gtk.STOCK_DIALOG_ERROR)
            return

        consulta += " from estados where "
        consulta += "estado = 'PENDIENTE DE APROBACIÓN'"

        try:
            cursor.execute (consulta)
        except:
            mensaje  = "Mientras se ejecutaba la aplicación ocurrió un error inesperado con origen en la excepción <b>%s</b> "
            mensaje += "y devolvió el siguiente error:\n\n<i>%s</i>."
            error ("Error inesperado", mensaje % (sys.exc_info () [0], sys.exc_info () [1]))
            return

        for item in items:
            cantidad = item [ITM_CANTIDAD]
            espesor  = item [ITM_ESPESOR]
            ancho    = item [ITM_ANCHO]
            largo    = item [ITM_LARGO]
            figura   = item [ITM_FIGURA]
            precio   = item [ITM_PARCIAL]

            consulta  = "insert into items ("
            consulta += "presupuesto,"
            consulta += "cantidad,"
            consulta += "espesor,"
            consulta += "ancho,"
            consulta += "largo,"
            consulta += "figura,"
            consulta += "precio"
            consulta += ") select "
            consulta += "p.numero,"
            consulta += "%d," % cantidad
            consulta += "c.espesor,"
            consulta += "c.ancho,"
            consulta += "c.largo,"
            consulta += "c.figura,"
            consulta += "%.2f" % precio
            consulta += " from "
            consulta += "carbones c,"
            consulta += "presupuestos p"
            consulta += " where "
            consulta += "c.espesor = %.2f and " % espesor
            consulta += "c.ancho = %.2f and "   % ancho
            consulta += "c.largo = %.2f and "   % largo
            consulta += "c.figura = %d and "    % figura
            consulta += "p.numero = %d"         % int (numero)

            try:
                cursor.execute (consulta)
            except:
                mensaje  = "Mientras se ejecutaba la aplicación ocurrió un error inesperado con origen en la excepción <b>%s</b> "
                mensaje += "y devolvió el siguiente error:\n\n<i>%s</i>."
                error ("Error inesperado", mensaje % (sys.exc_info () [0], sys.exc_info () [1]))
                return

        decrementar_existencia (numero)

        conexion.commit ()

        interface.ventana.limpiar_presupuesto.emit ('clicked')
        try:
            cursor.execute ('select max (numero) + 1 from presupuestos')
        except:
            mensaje  = "Mientras se ejecutaba la aplicación ocurrió un error inesperado con origen en la excepción <b>%s</b> "
            mensaje += "y devolvió el siguiente error:\n\n<i>%s</i>."
            error ("Error inesperado", mensaje % (sys.exc_info () [0], sys.exc_info () [1]))
            return

        (numero,) = cursor.fetchone ()
        interface.ventana.numero.set_markup ('<b>%s</b>' % str (numero).zfill (10))
        items.clear ()

        informar ("Listo. Se guardó el presupuesto", gtk.STOCK_DIALOG_INFO)

    def imprimir (*args):
        global configuacion

        contacto = interface.ventana.para.child.get_text  ()
        total    = interface.ventana.total.text.replace (',', '.')

        if not list (items):
            informar ("No se puede imprimir un presupuesto sin productos a presupuestar", gtk.STOCK_DIALOG_ERROR)
            return

        try:
            total = float (total)
        except ValueError:
            informar ("El precio total no es válido, el presupuesto no se puede imprimir", gtk.STOCK_DIALOG_ERROR)
            return

        if os.path.exists ('presupuesto.tpl'):
            archivo = open ('presupuesto.tpl', 'r')
            contenido = archivo.read ()
            archivo.close ()
        else:
            error ('No existe el archivo de plantilla', 'Por favor cree el archivo <b>presupuesto.tpl</b> y vuelva a intentar la operación.')
            return

        encabezado = ''
        fila       = ''
        pie        = ''
        for linea in contenido.splitlines ():
            if fila == '':
                if linea.find ('%(subtotal)s') == -1:
                    encabezado += '%s\n' % linea
                else:
                    fila = '%s\n' % linea
            else:
                pie += '%s\n' % linea

        duplicado = configuracion.duplicado
        copias = list ()
        copias.append ('ORIGINAL')
        if duplicado: copias.append ('DUPLICADO')

        for copia in copias:
            variables = dict (
                    numero   = str (numero).zfill (10),
                    fecha    = fecha,
                    contacto = contacto,
                    total    = ('%.02f' % total).ljust (8),
                    copia    = copia
                )

            salida = encabezado % variables

            for item in items:
                cantidad = item [ITM_CANTIDAD]
                espesor  = item [ITM_ESPESOR]
                ancho    = item [ITM_ANCHO]
                largo    = item [ITM_LARGO]
                figura   = item [ITM_FIGURA]
                precio   = item [ITM_PARCIAL]

                detalle   = 'CARBÓN ELÉCTRICO DE '
                detalle  += '%.02fx'  % float (espesor)
                detalle  += '%.02fx'  % float (ancho)
                detalle  += '%.02f'   % float (largo)
                detalle  += ' (%d)'   % int   (figura)

                reemplazos = dict (
                        cantidad = ('%d' % int (cantidad)).ljust (8),
                        detalle  = (detalle [0:45]).ljust (45),
                        precio   = ('%.02f' % float (precio)).ljust (6),
                        subtotal = ('%.02f' % (float (precio) * float (cantidad))).ljust (8)
                    )

                salida += fila % reemplazos

            salida += pie % variables

            imprimir (salida)

        interface.ventana.guardar.emit ('clicked')

    def enviar (*args):
        contacto  = interface.ventana.para.child.get_text ()
        total     = interface.ventana.total.text.replace (',', '.')
        remitente = interface.preferencias.remitente.text
        servidor  = interface.preferencias.servidor.text
        mensaje   = 'From: %s\r\nTo: %s\r\n\r\n' % (remitente, contacto)

        if not list (items):
            informar ("No se puede enviar por correo-e un presupuesto sin productos a presupuestar", gtk.STOCK_DIALOG_ERROR)
            return

        try:
            total = float (total)
        except ValueError:
            informar ("El precio total no es válido, el presupuesto no se puede enviar por correo-e", gtk.STOCK_DIALOG_ERROR)
            return

        if not re.match ('^[a-z]+[a-z0-9_-]*@[a-z]+[a-z0-9_-]\.[a-z]{3}(\.[a-z]{2})?$', contacto):
            informar ("La dirección de correo electrónico del destinatario no parece ser válida", gtk.STOCK_DIALOG_ERROR)
            return

        if os.path.exists ('presupuesto.email'):
            archivo = open ('presupuesto.email', 'r')
            contenido = archivo.read ()
            archivo.close ()
        else:
            error ('No existe el archivo de plantilla', 'Por favor cree el archivo <b>presupuesto.email</b> y vuelva a intentar la operación.')
            return

        encabezado = ''
        fila       = ''
        pie        = ''
        for linea in contenido.splitlines ():
            if fila == '':
                if linea.find ('%(subtotal)s') == -1:
                    encabezado += '%s\r\n' % linea
                else:
                    fila = '%s\r\n' % linea
            else:
                pie += '%s\r\n' % linea

        variables = dict (
                numero   = str (numero).zfill (10),
                fecha    = fecha,
                contacto = contacto,
                total    = ('%.02f' % total).ljust (8)
            )

        mensaje += encabezado % variables

        for item in items:
            cantidad = item [ITM_CANTIDAD]
            espesor  = item [ITM_ESPESOR]
            ancho    = item [ITM_ANCHO]
            largo    = item [ITM_LARGO]
            figura   = item [ITM_FIGURA]
            precio   = item [ITM_PARCIAL]

            detalle   = 'CARBÓN ELÉCTRICO DE '
            detalle  += '%.02fx'  % float (espesor)
            detalle  += '%.02fx'  % float (ancho)
            detalle  += '%.02f'   % float (largo)
            detalle  += ' (%d)'  % int   (figura)

            reemplazos = dict (
                    cantidad = ('%d' % int (cantidad)).ljust (8),
                    detalle  = (detalle [0:43]).ljust (43),
                    precio   = ('%.02f' % float (precio)).ljust (6),
                    subtotal = ('%.02f' % (float (precio) * float (cantidad))).ljust (8)
                )

            mensaje += fila % reemplazos

        mensaje += pie % variables

        try:
            agente = smtplib.SMTP (servidor)
            agente.sendmail (remitente, contacto, mensaje)
        except Exception, excepcion:
            codigo, descripcion = excepcion
            error ('No se puede enviar el presupuesto', 'No se puede enviar el presupuesto porque el agente de correo-e respondió con el siguiente error: %s (%d)' % (descripcion, codigo))
            return

        agente.quit ()

        interface.ventana.guardar.emit ('clicked')

    def ver (*args):
        seleccion     = interface.ventana.presupuestos.get_selection ()
        (dummy, iter) = seleccion.get_selected ()

        if not iter:
            informar ("Primero debe seleccionar el presupuesto.")
            return

        numero = presupuestos.get_value (iter, PRE_NUMERO)

        if os.path.exists ('presupuesto.tpl'):
            archivo = open ('presupuesto.tpl', 'r')
            contenido = archivo.read ()
            archivo.close ()
        else:
            error ('No existe el archivo de plantilla', 'Por favor cree el archivo <b>presupuesto.tpl</b> y vuelva a intentar la operación.')
            return

        bufer      = interface.presupuesto.presupuesto.buffer
        encabezado = ''
        fila       = ''
        pie        = ''
        for linea in contenido.splitlines ():
            if fila == '':
                if linea.find ('%(subtotal)s') == -1:
                    encabezado += '%s\n' % linea
                else:
                    fila = '%s\n' % linea
            else:
                pie += '%s\n' % linea

        sql  = 'select '
        sql += 'fecha as fecha,'
        sql += 'contacto as contacto,'
        sql += 'total as total '
        sql += 'from '
        sql += 'presupuestos '
        sql += 'where '
        sql += 'numero = %d' % int (numero)

        try:
            cursor.execute (sql)
        except:
            mensaje  = "Mientras se ejecutaba la aplicación ocurrió un error inesperado con origen en la excepción <b>%s</b> "
            mensaje += "y devolvió el siguiente error:\n\n<i>%s</i>."
            error ("Error inesperado", mensaje % (sys.exc_info () [0], sys.exc_info () [1]))
            return

        registro = cursor.fetchone ()
        fecha    = registro [0]
        contacto = registro [1]
        total    = registro [2]

        variables = dict (
                numero   = str (numero).zfill (10),
                fecha    = fecha,
                contacto = contacto,
                total    = ('%.02f' % total).ljust (8),
                copia    = 'COPIA ALMACENADA'
            )

        salida = encabezado % variables

        sql  = 'select '
        sql += 'cantidad,'
        sql += 'espesor,'
        sql += 'ancho,'
        sql += 'largo,'
        sql += 'figura,'
        sql += 'precio '
        sql += 'from '
        sql += 'items '
        sql += 'where '
        sql += 'presupuesto = %d' % int (numero)

        try:
            cursor.execute (sql)
        except:
            mensaje  = "Mientras se ejecutaba la aplicación ocurrió un error inesperado con origen en la excepción <b>%s</b> "
            mensaje += "y devolvió el siguiente error:\n\n<i>%s</i>."
            error ("Error inesperado", mensaje % (sys.exc_info () [0], sys.exc_info () [1]))
            return

        for (
                cantidad,
                espesor,
                ancho,
                largo,
                figura,
                precio,
            ) in cursor:
            detalle   = 'CARBÓN ELÉCTRICO DE '
            detalle  += '%.02fx'  % float (espesor)
            detalle  += '%.02fx'  % float (ancho)
            detalle  += '%.02f'   % float (largo)
            detalle  += ' (%d)'   % int   (figura)

            reemplazos = dict (
                    cantidad = ('%d' % int (cantidad)).ljust (8),
                    detalle  = (detalle [0:45]).ljust (45),
                    precio   = ('%.02f' % float (precio)).ljust (6),
                    subtotal = ('%.02f' % (float (precio) * float (cantidad))).ljust (8)
                )

            salida += fila % reemplazos

        salida += pie % variables

        bufer.set_text (salida)

        estilo = bufer.create_tag (None, font = 'Courier 11')
        inicio, fin = bufer.get_bounds ()

        bufer.apply_tag (estilo, inicio, fin)

        interface.presupuesto.set_transient_for (interface.ventana)
        interface.presupuesto.run ()
        interface.presupuesto.hide ()

    def ayuda (*args):
        webbrowser.open ("ayuda.html")

    def acerca (*args):
        interface.acerca.set_transient_for (interface.ventana)
        interface.acerca.run ()
        interface.acerca.hide ()

    def preferencias (*args):
        global configuracion

        interface.preferencias.set_transient_for (interface.ventana)
        respuesta = interface.preferencias.run  ()

        interface.preferencias.hide ()

        if respuesta == gtk.RESPONSE_OK:
            archivo = open ('cp.ini', "w")

            archivo.write ('[general]\n')

            cargar    = interface.preferencias.cargar.active    and 'Yes' or 'No'
            duplicado = interface.preferencias.duplicado.active and 'Yes' or 'No'
            minimo    = interface.preferencias.minimo.get_value ()
            archivo.write ('cargar = %s\n'    % cargar)
            archivo.write ('duplicado = %s\n' % duplicado)
            archivo.write ('minimo = %d\n'    % int (minimo))
            archivo.write ('remitente = %s\n' % interface.preferencias.remitente.text)
            archivo.write ('servidor = %s\n'  % interface.preferencias.servidor.text)

            archivo.write ('\n[colores]\n')

            color = interface.preferencias.pendiente.get_color ()
            configuracion.colores.frente.pendiente = '#%04X%04X%04X' % (color.red, color.green, color.blue)
            archivo.write ('pendiente_frente = %s\n' % configuracion.colores.frente.pendiente)

            color = interface.preferencias.aprobado.get_color ()
            configuracion.colores.frente.aprobado = '#%04X%04X%04X' % (color.red, color.green, color.blue)
            archivo.write ('aprobado_frente = %s\n' % configuracion.colores.frente.aprobado)

            color = interface.preferencias.cumplido.get_color ()
            configuracion.colores.frente.cumplido = '#%04X%04X%04X' % (color.red, color.green, color.blue)
            archivo.write ('cumplido_frente = %s\n' % configuracion.colores.frente.cumplido)

            color = interface.preferencias.rechazado.get_color ()
            configuracion.colores.frente.rechazado = '#%04X%04X%04X' % (color.red, color.green, color.blue)
            archivo.write ('rechazado_frente = %s\n' % configuracion.colores.frente.rechazado)

            color = interface.preferencias.fondo_pendiente.get_color ()
            configuracion.colores.fondo.pendiente = '#%04X%04X%04X' % (color.red, color.green, color.blue)
            archivo.write ('pendiente_fondo = %s\n' % configuracion.colores.fondo.pendiente)

            color = interface.preferencias.fondo_aprobado.get_color ()
            configuracion.colores.fondo.aprobado = '#%04X%04X%04X' % (color.red, color.green, color.blue)
            archivo.write ('aprobado_fondo = %s\n' % configuracion.colores.fondo.aprobado)

            color = interface.preferencias.fondo_cumplido.get_color ()
            configuracion.colores.fondo.cumplido = '#%04X%04X%04X' % (color.red, color.green, color.blue)
            archivo.write ('cumplido_fondo = %s\n' % configuracion.colores.fondo.cumplido)

            color = interface.preferencias.fondo_rechazado.get_color ()
            configuracion.colores.fondo.rechazado = '#%04X%04X%04X' % (color.red, color.green, color.blue)
            archivo.write ('rechazado_fondo = %s\n' % configuracion.colores.fondo.rechazado)

            archivo.write ('\n[fuentes]\n')

            configuracion.fuentes.pendiente = interface.preferencias.fuente_pendiente.get_font_name ()
            archivo.write ('pendiente = %s\n' % configuracion.fuentes.pendiente)
            configuracion.fuentes.aprobado = interface.preferencias.fuente_aprobado.get_font_name ()
            archivo.write ('aprobado = %s\n' % configuracion.fuentes.aprobado)
            configuracion.fuentes.cumplido = interface.preferencias.fuente_cumplido.get_font_name ()
            archivo.write ('cumplido = %s\n' % configuracion.fuentes.cumplido)
            configuracion.fuentes.rechazado = interface.preferencias.fuente_rechazado.get_font_name ()
            archivo.write ('rechazado = %s\n' % configuracion.fuentes.rechazado)

            archivo.close ()

    def nuevo_catalogo (*args):
        interface.catalogo.set_transient_for (interface.ventana)
        interface.catalogo.catalogo.set_text ('')
        respuesta = interface.catalogo.run ()
        interface.catalogo.hide ()
        if not respuesta == gtk.RESPONSE_OK: return

        catalogo = interface.catalogo.catalogo.text
        if not catalogo: return

        consulta = "insert into catalogos values ('%s')" % catalogo

        try:
            cursor.execute (consulta)
        except sql.IntegrityError:
            error ('No se puede agregar el catálogo', 'El catálogo no se pudo agregar poque ya existe.')
            return
        except:
            mensaje  = "Mientras se ejecutaba la aplicación ocurrió un error inesperado con origen en la excepción <b>%s</b> "
            mensaje += "y devolvió el siguiente error:\n\n<i>%s</i>."
            error ("Error inesperado", mensaje % (sys.exc_info () [0], sys.exc_info () [1]))
            return

        conexion.commit ()

        interface.ventana.catalogo.append_text (catalogo)
        interface.formulario.catalogo.append_text (catalogo)

    def consola (*args):
        os.spawnl (os.P_NOWAIT, 'consola.bat')

    def importar (*args):
        selector = gtk.FileChooserDialog ('Por favor, seleccione el archivo de comandos SQL que desea ejecutar',
                interface.formulario,
                gtk.FILE_CHOOSER_ACTION_OPEN,
                    (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
                     gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)
            )

        respuesta = selector.run ()
        archivo = selector.get_filename ()
        selector.destroy ()

        if respuesta == gtk.RESPONSE_ACCEPT:
            importar (archivo)

    def salir (*args):
        if icono:
            icono.set_visible (False)
        cursor.close ()
        conexion.close ()
        gtk.main_quit ()

if __name__ == "__main__":
    warnings.filterwarnings ('ignore', '', Warning, __name__)

    crear = 0
    if not os.path.exists ('cp.db'):
        crear = 1

    conexion = sql.connect ('cp.db')
    cursor   = conexion.cursor ()

    try:
        interface = gui ('cp.xml', callbacks)
    except:
        interface = gui ('cp.glade', callbacks)

    cfg = ConfigParser ()

    interface.ventana.carbones.set_markup_column (CAR_DESCRIPCION)
    interface.ventana.carbones.set_pixbuf_column (CAR_DIBUJO)

    interface.ventana.carbones.set_model (carbones)

    columnas = [
            'Miniatura',
            'Catálogo',
            'Marca',
            'Código',
            'Producto',
            'Espesor',
            'Ancho',
            'Largo',
            'Figura',
            'Existencia',
            'Precio'
        ]

    interface.ventana.productos.set_model (productos)
    columna = gtk.TreeViewColumn (columnas [PRO_MINIATURA])
    celda = gtk.CellRendererPixbuf ()
    columna.pack_start (celda, False)
    columna.set_attributes (celda, pixbuf = PRO_MINIATURA)
    interface.ventana.productos.append_column (columna)
    for n_columna in range (PRO_CATALOGO, PRO_PRECIO + 1):
        columna = gtk.TreeViewColumn (columnas [n_columna])
        celda = gtk.CellRendererText ()
        if n_columna != PRO_CATALOGO:
            celda.set_property ('editable', True)
            celda.connect ('editing-started', corregir_alto_celda)
            celda.connect ('edited', editar_celda_productos, n_columna)
        columna.pack_start (celda, False)
        columna.set_resizable (True)
        columna.set_attributes (celda, text = n_columna)
        columna.set_sort_column_id (n_columna)
        interface.ventana.productos.append_column (columna)
        #if n_columna == PRO_FIGURA or n_columna == PRO_EXISTENCIA:
        #    columna.set_cell_data_func (celda, formatear_celda_entero, n_columna)
        if n_columna >= PRO_ESPESOR and n_columna <= PRO_LARGO or n_columna == PRO_PRECIO:
            columna.set_cell_data_func (celda, formatear_celda, n_columna)

    columnas = [
            'Miniatura',
            'Cantidad',
            'Detalle',
            'Precio parcial',
            'Precio total'
        ]

    interface.ventana.items.set_model (items)
    columna = gtk.TreeViewColumn (columnas [ITM_MINIATURA])
    celda = gtk.CellRendererPixbuf ()
    columna.pack_start (celda, False)
    columna.set_attributes (celda, pixbuf = ITM_MINIATURA)
    interface.ventana.items.append_column (columna)

    for n_columna in range (ITM_CANTIDAD, ITM_TOTAL + 1):
        columna = gtk.TreeViewColumn (columnas [n_columna])
        celda = gtk.CellRendererText ()
        if n_columna in [ ITM_CANTIDAD, ITM_PARCIAL ]:
            celda.set_property ('editable', True)
            celda.connect ('editing-started', corregir_alto_celda)
            celda.connect ('edited', editar_celda_items, n_columna)
        columna.pack_start (celda, False)
        columna.set_resizable (True)
        columna.set_attributes (celda, text = n_columna)
        interface.ventana.items.append_column (columna)
        if n_columna in [ ITM_PARCIAL, ITM_TOTAL ]:
            columna.set_cell_data_func (celda, formatear_celda, n_columna)

    columnas = [
            'Número',
            'Fecha',
            'Contacto',
            'Estado',
            'Total'
        ]

    interface.ventana.presupuestos.set_model (presupuestos)

    for n_columna in range (PRE_NUMERO, PRE_TOTAL + 1):
        columna = gtk.TreeViewColumn (columnas [n_columna])
        celda = gtk.CellRendererText ()
        columna.pack_start (celda, False)
        columna.set_resizable (True)
        columna.set_attributes (celda, text = n_columna, foreground = PRE_FRENTE, cell_background = PRE_FONDO, font = PRE_FUENTE)
        interface.ventana.presupuestos.append_column (columna)
        columna.set_sort_column_id (n_columna)
        if n_columna == PRE_TOTAL:
            columna.set_cell_data_func (celda, formatear_celda, n_columna)
        if n_columna == PRE_NUMERO:
            columna.set_cell_data_func (celda, formatear_celda_numero, n_columna)

    hoy = date.today ()
    fecha = '%02d/%02d/%04d' % (hoy.day, hoy.month, hoy.year)

    interface.ventana.fecha.set_markup ('<b>%s</b>' % fecha)

    interface.ventana.catalogo.set_active    (0)
    interface.ventana.estado.set_active      (0)
    interface.formulario.catalogo.set_active (0)

    recortada = '%s/no.%s' % (base, extension)
    dibujo = gtk.gdk.pixbuf_new_from_file (recortada)
    interface.formulario.dibujo.set_from_pixbuf (dibujo)

    try:
        cfg.read ('cp.ini')
        configuracion.cargar                   = cfg.getboolean ('general', 'cargar')
        configuracion.duplicado                = cfg.getboolean ('general', 'duplicado')
        configuracion.minimo                   = cfg.getint ('general', 'minimo')
        configuracion.remitente                = cfg.get ('general', 'remitente')
        configuracion.servidor                 = cfg.get ('general', 'servidor')
        configuracion.colores.frente.pendiente = cfg.get ('colores', 'pendiente_frente')
        configuracion.colores.frente.aprobado  = cfg.get ('colores', 'aprobado_frente')
        configuracion.colores.frente.cumplido  = cfg.get ('colores', 'cumplido_frente')
        configuracion.colores.frente.rechazado = cfg.get ('colores', 'rechazado_frente')
        configuracion.colores.fondo.pendiente  = cfg.get ('colores', 'pendiente_fondo')
        configuracion.colores.fondo.aprobado   = cfg.get ('colores', 'aprobado_fondo')
        configuracion.colores.fondo.cumplido   = cfg.get ('colores', 'cumplido_fondo')
        configuracion.colores.fondo.rechazado  = cfg.get ('colores', 'rechazado_fondo')
        configuracion.fuentes.pendiente        = cfg.get ('fuentes', 'pendiente')
        configuracion.fuentes.aprobado         = cfg.get ('fuentes', 'aprobado')
        configuracion.fuentes.cumplido         = cfg.get ('fuentes', 'cumplido')
        configuracion.fuentes.rechazado        = cfg.get ('fuentes', 'rechazado')
    except Exception, mensaje:
        informar ("Se produjo un error mientras se abría el archivo cp.ini y se leía la configuración", gtk.STOCK_DIALOG_WARNING)
        mensaje  = "Mientras se ejecutaba la aplicación ocurrió un error inesperado con origen en la excepción <b>%s</b> "
        mensaje += "y devolvió el siguiente error:\n\n<i>%s</i>."
        error ("Error inesperado", mensaje % (sys.exc_info () [0], sys.exc_info () [1]))

    color = obtener_color_gdk (configuracion.colores.frente.pendiente)
    interface.preferencias.pendiente.set_color (color)
    color = obtener_color_gdk (configuracion.colores.frente.aprobado)
    interface.preferencias.aprobado.set_color (color)
    color = obtener_color_gdk (configuracion.colores.frente.cumplido)
    interface.preferencias.cumplido.set_color (color)
    color = obtener_color_gdk (configuracion.colores.frente.rechazado)
    interface.preferencias.rechazado.set_color (color)

    color = obtener_color_gdk (configuracion.colores.fondo.pendiente)
    interface.preferencias.fondo_pendiente.set_color (color)
    color = obtener_color_gdk (configuracion.colores.fondo.aprobado)
    interface.preferencias.fondo_aprobado.set_color (color)
    color = obtener_color_gdk (configuracion.colores.fondo.cumplido)
    interface.preferencias.fondo_cumplido.set_color (color)
    color = obtener_color_gdk (configuracion.colores.fondo.rechazado)
    interface.preferencias.fondo_rechazado.set_color (color)

    interface.preferencias.fuente_pendiente.set_font_name (configuracion.fuentes.pendiente)
    interface.preferencias.fuente_aprobado.set_font_name (configuracion.fuentes.aprobado)
    interface.preferencias.fuente_cumplido.set_font_name (configuracion.fuentes.cumplido)
    interface.preferencias.fuente_rechazado.set_font_name (configuracion.fuentes.rechazado)

    interface.preferencias.remitente.set_text (configuracion.remitente)
    interface.preferencias.servidor.set_text (configuracion.servidor)
    interface.preferencias.cargar.set_active (configuracion.cargar)
    interface.preferencias.minimo.set_value (configuracion.minimo)

    if configuracion.cargar: interface.ventana.buscar.emit ('clicked')

    destinatarios = gtk.ListStore (str)
    interface.ventana.para.set_model (destinatarios)
    interface.ventana.para.set_text_column (0)

    try:
        cursor.execute ('select catalogo from catalogos order by catalogo')
    except:
        mensaje  = "Mientras se ejecutaba la aplicación ocurrió un error inesperado con origen en la excepción <b>%s</b> "
        mensaje += "y devolvió el siguiente error:\n\n<i>%s</i>."
        error ("Error inesperado", mensaje % (sys.exc_info () [0], sys.exc_info () [1]))

    interface.ventana.catalogo.append_text (TODOS)
    for catalogo, in cursor:
        interface.ventana.catalogo.append_text (catalogo)
        interface.formulario.catalogo.append_text (catalogo)

    try:
        cursor.execute ('select estado from estados order by estado')
    except:
        mensaje  = "Mientras se ejecutaba la aplicación ocurrió un error inesperado con origen en la excepción <b>%s</b> "
        mensaje += "y devolvió el siguiente error:\n\n<i>%s</i>."
        error ("Error inesperado", mensaje % (sys.exc_info () [0], sys.exc_info () [1]))

    interface.ventana.estado.append_text ('TODOS LOS ESTADOS')
    for estado, in cursor:
        interface.ventana.estado.append_text (estado)

    try:
        cursor.execute ('select max (numero) + 1 from presupuestos')
        (serial,) = cursor.fetchone ()
    except:
        serial = 1
        mensaje  = "Mientras se ejecutaba la aplicación ocurrió un error inesperado con origen en la excepción <b>%s</b> "
        mensaje += "y devolvió el siguiente error:\n\n<i>%s</i>."
        error ("Error inesperado", mensaje % (sys.exc_info () [0], sys.exc_info () [1]))

    numero = serial or 1
    interface.ventana.numero.set_markup ('<b>%s</b>' % str (numero).zfill (10))

    try:
        cursor.execute ('select correo_e from contactos order by correo_e')
    except:
        mensaje  = "Mientras se ejecutaba la aplicación ocurrió un error inesperado con origen en la excepción <b>%s</b> "
        mensaje += "y devolvió el siguiente error:\n\n<i>%s</i>."
        error ("Error inesperado", mensaje % (sys.exc_info () [0], sys.exc_info () [1]))

    for correo_e, in cursor:  interface.ventana.para.append_text (correo_e)

    icono = gtk.status_icon_new_from_file ('icono.ico')
    icono.set_tooltip ("Carbones Patagonia %s" % VERSION)
    icono.connect ("activate", activar_icono_aplicacion)

    interface.ventana.show ()

    if crear:
        importar ('cp.sql')

    gtk.main ()
